#ifndef ENTITY_H_
#define ENTITY_H_

#include <string>
#include <map>

#include "entityinterface.h"
#include "collisionbox.h"
#include "definitions.h"
#include "draw.h"
#include "util.h"
#include "entitylogic.h"

enum 
{
	
	SB_UP = 1,
	SB_DOWN,
	SB_LEFT,
	SB_DOWNRUN,
	SB_LEFTRUN,
	SB_RIGHT,
	SB_RIGHTRUN,
	
};

enum 
{
	
	RUN_LEFTIDLE = 5,
	RUN_RIGHTIDLE = 7,
	RUN_DOWNIDLE = 4,
	
};

enum
{
	
	IDLE_LEFTRUN = 3,
	IDLE_RIGHTRUN = 6,
	IDLE_DOWNRUN = 2,
	IDLE_UPRUN = 1,
	
};

enum 
{
	
	CID_ENTITY = 0,
	CID_PLAYER,
	CID_ENEMY
	
};

#define PAIN_FLASH_SPEED 45

class CEntity
{

protected:
	
	CEntityInterface * m_pEntityInterface;

	CCollisionBox * m_pCollisionBox;
	
	CEntityLogic * m_pEntityLogic;
	
	Vector2D m_Pos;
	Vector2D m_SizeOverride;
	Vector2D m_ConstVel, m_PhysVel;
	Vector2D m_ShadowOffset, m_ShadowScale;
	
	bool m_bOnScreen;
	
	std::vector< CMaterial * > m_pSprites;
	std::map< int, int > m_SpriteBindings;
	
	int m_CurFrame, m_TimeNextFrame, m_TimeLastFrame, m_CurSprite, m_CurSpriteBind;
	float m_AnimRate;
	bool m_bAnimating;
	
	int m_UniqueID;
	
	std::string m_Class;
	int m_ClassID;
	
	bool m_bEffectedByGravity, m_bEffectedByAirFriction;
	
	int m_DrawLayer;
	
	bool m_bXFlipped;
	
	bool m_bHasShadow;
	
	CMaterial * m_pShadowMat;
	
	Vector2D m_Color;
	double m_Alpha;
	
	bool m_bPainFlashing;
	bool m_bMidPainFlash;
	int m_DonePainFlashing, m_NextMidPainFlash;
	
	bool m_bDoEntLogic;
	
	bool m_bDeathFade;
	bool m_bDeathFadeOut;
	
	Vector2D m_ST1, m_ST2;
	bool m_bSTSet;
	
public:
	
	CEntity();
	virtual ~CEntity();
	
	inline void SetST( float x1, float y1, float x2, float y2 )
	{
		m_bSTSet = true;
		
		m_ST1.SetXY( x1, y1 );
		m_ST2.SetXY( x2, y2 );
		
	}
	
	inline void SetEntityLogic( CEntityLogic * l )
	{
		
		m_pEntityLogic = l;
		
	}
	
	inline void SetEntityDoLogic( bool b )
	{
		
		m_bDoEntLogic = b;
		
	}
	
	inline bool DeathFadedOut()
	{
		
		return ( m_bDeathFadeOut && ( int )m_Alpha == 1 );
		
	}
	
	inline bool DeathFading()
	{
		
		return m_bDeathFade;
		
	}
	
	inline void DeathFade()
	{
		
		m_bDeathFade = true;
		
	}
	
	inline bool VelocityIsZero()
	{
		
		if( fabs( m_PhysVel.x ) < .0001 &&
			fabs( m_PhysVel.y ) < .0001 && 
			fabs( m_ConstVel.x ) < .0001 &&
			fabs( m_ConstVel.y ) < .0001 )
			return true;
		
		return false;
		
	}
	
	inline void PainFlash( int time )
	{
		
		m_bPainFlashing = true;
		m_bMidPainFlash = false;
		m_DonePainFlashing = SDL_GetTicks() + time;
		m_NextMidPainFlash = SDL_GetTicks() + PAIN_FLASH_SPEED;
		
	}
	
	void ApplyPhysicsVelocity();
	void ApplyConstantVelocity();
	void ApplyAirFriction();
	void ApplyGravity();
	
	void AnimateThink();
	
	inline bool operator== ( CEntity * e )
	{
		
		return ( m_UniqueID == e->m_UniqueID );
		
	}

	inline bool operator!= ( CEntity * e )
	{
		
		return ( m_UniqueID != e->m_UniqueID );
		
	}
	
	inline bool IsEnemy()
	{
		
		return ( m_ClassID == CID_ENEMY );
		
	}
	
	inline bool IsPlayer()
	{
		
		return ( m_ClassID == CID_PLAYER );
		
	}
	
	inline void SetClassID( int cid )
	{
		
		m_ClassID = cid;
		
	}
	
	inline void Scale( float x, float y, int sprite = 0, int frame = 0 )
	{
		
		m_SizeOverride.x = m_pSprites[sprite]->GetFrameWidth( frame ) * x;
		m_SizeOverride.y = m_pSprites[sprite]->GetFrameHeight( frame ) * y;
		
	}
	
	inline void SetShadowOffset( double x, double y )
	{
		
		m_ShadowOffset.x = x;
		m_ShadowOffset.y = y;
		
	}
	
	inline void SetShadowScale( double w, double h )
	{
		
		m_ShadowScale.x = w;
		m_ShadowScale.y = h;
		
	}
	
	inline void SetUniqueID( int id )
	{
		
		m_UniqueID = id;
		
	}
	
	inline void SetHasShadow( bool b )
	{
		
		m_bHasShadow = b;
		
		if( b )
		{
			
			m_pShadowMat = m_pEntityInterface->Graphics()->Find( "Graphics/Effects/shadow.png" );
			
		}
		
	}
	
	inline void SetSizeOverride( double w, double h )
	{
		
		m_SizeOverride.SetXY( w, h );
		
	}
	
	inline bool IsFlipped()
	{
		
		return m_bXFlipped;
		
	}
	
	inline void Flip()
	{
	
		m_bXFlipped = !m_bXFlipped;
		
	}
	
	inline void SetDrawLayer( int d )
	{
	
		m_DrawLayer = d;
		
	}
	
	inline const int & GetDrawLayer()
	{
	
		return m_DrawLayer;
		
	}
	
	inline const std::string & GetClass()
	{
	
		return m_Class;
		
	}
	
	inline int GetUniqueID()
	{
	
		return m_UniqueID;
		
	}
	
	inline bool CanDraw()
	{
	
		return ( m_bOnScreen && m_pSprites.size() > 0 );
		
	}
	
	inline int GetCurrentSprite()
	{
		
		return m_CurSprite;
		
	}
	
	inline bool IsCollidingWith( CCollisionBox * b, CCollisionData & cd )
	{
		
		return m_pCollisionBox->IsCollidingWith( b, cd );
		
	}
	
	inline bool IsCollidingWith( CEntity * e2, CCollisionData & cd )
	{
		
		return m_pCollisionBox->IsCollidingWith( e2->m_pCollisionBox, cd );
		
	}

	
	inline void RunAnim()
	{
		
		int sb = 0;
		
		switch( m_CurSpriteBind ) 
		{
			
			case SB_LEFT:
				sb = RUN_LEFTIDLE;
				break;
			
			case SB_RIGHT:
				sb = RUN_RIGHTIDLE;
				break;
			
			case SB_DOWN:
				sb = RUN_DOWNIDLE;
				break;
				
		}
		
		if( sb > 0 )
			SetCurrentSpriteBind( sb );
		
	}
	
	inline void IdleAnim()
	{
		
		int sb = 0;

		switch( m_CurSpriteBind ) 
		{
			
			case SB_LEFTRUN:
				sb = IDLE_LEFTRUN;
				break;
			
			case SB_RIGHTRUN:
				sb = IDLE_RIGHTRUN;
				break;
				
			case SB_DOWNRUN:
				sb = IDLE_DOWNRUN;
				break;
			
		}
		
		if( sb > 0 )
			SetCurrentSpriteBind( sb );
		
	}
	
	inline int GetCurrentSpriteBind()
	{
		
		return m_CurSpriteBind;
		
	}
	
	inline CCollisionBox * GetCollisionBox()
	{
		
		return m_pCollisionBox;
		
	}
	
	inline void SyncCollisionBoxPos()
	{
		
		if( m_pCollisionBox )
			m_pCollisionBox->SetPos( m_Pos.x, m_Pos.y );
		
	}
	
	inline Vector2D GetSize( int i = -1 )
	{
		
		Vector2D size;
		
		if( i < 0 )
			i = m_CurFrame;
		
		if( m_SizeOverride.x > -1 )
		{
		
			size.x = m_SizeOverride.x;
			size.y = m_SizeOverride.y;
		
		} else
		{
			
			size.x = m_pSprites[m_CurSprite]->GetFrameWidth( i );
			size.y = m_pSprites[m_CurSprite]->GetFrameHeight( i );
			
		}
		
		return size;
		
	}
	
	inline Vector2D GetCenter()
	{
		
		Vector2D size = GetSize();
		
		size.x *= .5;
		size.y *= .5;
		
		return size + m_Pos;
		
	}
	
	inline void ShiftCollisionBoxToBottom()
	{
		
		m_pCollisionBox->ShiftToBottom( GetSize().y );
		
		
	}
	
	inline void FitCollisionBoxToSprite2( int i, float xo, float yo, float mul = 1.0f, float ymul = 1.0f )
	{
		
		Vector2D size = GetSize( i );
		
		m_pCollisionBox->SetEnabled( true );
		m_pCollisionBox->FitBox2( xo, yo, size.x, size.y, mul, ymul );
		
	}
	
	inline void FitCollisionBoxToSprite( int i, float mul = 1.0f, float ymul = 1.0f )
	{
		
		int w = 0;
		int h = 0;
		
		if( m_SizeOverride.x > -1 || i < 0 )
		{
			
			w = m_SizeOverride.x;
			h = m_SizeOverride.y;
			
		} else
		{
			
			w = m_pSprites[i]->GetFrameWidth( 0 );
			h = m_pSprites[i]->GetFrameHeight( 0 );
		
		}
	
		m_pCollisionBox->SetEnabled( true );
		m_pCollisionBox->FitBox( w, h, mul, ymul );
		
	}
	
	inline bool CanAnimate()
	{
		
		return ( m_bAnimating || ( m_CurSprite >= 0 && m_pSprites[m_CurSprite]->GetFrameCount() > 1 ) );
		
	}
	
	inline void SetAnimating( bool b )
	{
		
		m_bAnimating = b;
		
	}
	
	inline void SetCurrentSprite( int i )
	{
		
		if( i < 0 || i >= m_pSprites.size() )
			return;
	
		if( m_CurSprite == i )
			return;
		
		m_CurSprite = i;
		m_CurFrame = 0;
		
		m_CurSpriteBind = -1;
		
		if( m_pSprites[i]->GetFrameCount() > 0 )
		{
			
			m_bAnimating = true;
	
			m_TimeNextFrame = SDL_GetTicks() + m_pSprites[i]->GetFramePause( 0 );
			
		}
		
	}
	
	inline void ChangeFlipBasedOnVelocity()
	{
		
		if( m_PhysVel.x < 0 )
			m_bXFlipped = false;
		
		if( m_PhysVel.x > 0 )
			m_bXFlipped = true;
		
	}
	
	inline void BindSprite( CMaterial * m, int e, bool current = false )
	{
		
		int i = m_pSprites.size();
		m_pSprites.push_back( m );
		
		m_SpriteBindings[e] = i;
		
		if( current )
		{
			SetCurrentSpriteBind( e );
		}
		
	}
	
	inline void SetCurrentSpriteBind( int e )
	{
		
		int b = e;
		
		m_bXFlipped = false;
		
		switch( e )
		{
			
			case SB_RIGHT:
			m_bXFlipped = true;
			b = SB_LEFT;
			break;
			
			case SB_RIGHTRUN:
			m_bXFlipped = true;
			b = SB_LEFTRUN;
			break;
			
		}
		
		int s = 0;
		
		try
		{
		
			s = m_SpriteBindings.at( b );
		
		} catch( std::exception & err )
		{ }
			
		SetCurrentSprite( s );
		m_CurSpriteBind = e;
		
	}
	
	inline void AddSprite( CMaterial * m, bool current = false )
	{
	
		m_pSprites.push_back( m );
		
		if( current )
		{
		
			SetCurrentSprite( m_pSprites.size() - 1 );
			
		}
		
	}
	
	inline void SetColor( double r, double g, double b, double a )
	{
		
		m_Color.SetXYZ( r, g, b );
		m_Alpha = a;
		
	}
	
	inline void SetPos( const Vector2D & v )
	{
		
		m_Pos = v;
		
	}
	
	inline void SetPos( double x, double y )
	{
	
		m_Pos.SetXY( x, y );
		
	}
	
	inline const Vector2D & GetPos()
	{
		
		return m_Pos;
		
	}
	
	inline void SetConstantVel( double x, double y )
	{
	
		m_ConstVel.SetXY( x, y );
		
	}
	
	inline void AddPhysicsVel( double x, double y )
	{
	
		m_PhysVel.x += x;
		m_PhysVel.y += y;
		
	}
	
	inline void SetPhysicsVel( double x, double y )
	{
	
		m_PhysVel.SetXY( x, y );
		
	}
	
	inline void SetEntityInterface( CEntityInterface * e )
	{
	
		m_pEntityInterface = e;
		
	}
	
	inline bool OnScreen() 
	{
	
		return m_bOnScreen;
		
	}
	
	virtual void BaseThink();
	virtual void BaseDraw();
	
	virtual void Think() { BaseThink(); }
	virtual void Draw() { BaseDraw(); }

};

#endif
